import Taro from '@tarojs/taro'
import Queue from '../utils/Queue'
import CacheManager from './CacheManager'

import emptyImg from '../images/empty.jpg'
/**
 * 翻转管理器
 */
export default class FlipManager {

    cacheManager: CacheManager

    isLandscape: boolean
    data: string[]
    page: number
    total: number
    scene: {
        width: number,
        height: number
    } = { width: 0, height: 0 }
    dataChangedListener: (images: PageImages) => void
    animationChangedListener: (animations: PageAnimations) => void
    animationProcessListener: AnimationProcessListener

    animationRunning: boolean = false
    startDuration: number = 50
    dataDuration: number = 250
    procDuration: number = 400
    endedDuration: number = 50
    startTimer: number
    processTimer: number
    endedTimer: number

    touchEvent: boolean = false
    fragmentTimer: number
    fragmentRunning: boolean = false
    animationQueue: Queue<AnimationData>
    animationQueueRuning: boolean = false
    animationQueueTimer: number

    constructor(data: string[], isLandscape: boolean) {
        this.data = data
        this.total = this.data.length
        this.page = 0
        this.isLandscape = isLandscape
        this.animationQueue = new Queue<AnimationData>(3)
    }

    init = () => {
        this.dataChangedListener(this.getFlipPages())
        this.animationChangedListener(this.createOpacityAnimation(0))
    }

    destroy = () => {
        clearTimeout(this.animationQueueTimer)
        clearTimeout(this.fragmentTimer)
        this.animationQueue.clear()
        this.animationQueueRuning = false
        this.fragmentRunning = false
        this.animationRunning = false
    }

    async getSceneLayout(): Promise<any> {
        const layout = await Taro.getSystemInfo().then((sys: Taro.getSystemInfo.Promised) => {
            this.scene = {
                width: this.isLandscape ? sys.windowHeight : sys.windowWidth,
                height: this.isLandscape ? sys.windowWidth : sys.windowHeight
            }
            const width = this.isLandscape ? sys.windowHeight + 'px' : sys.windowWidth + 'px'
            const height = this.isLandscape ? sys.windowWidth + 'px' : sys.windowHeight + 'px'
            const offset = 0.5 * Math.abs(sys.windowHeight - sys.windowWidth)
            const top = this.isLandscape ? offset + 'px' : '0px'
            const left = this.isLandscape ? (-1 * offset) + 'px' : '0px'
            const scaleMode = this.isLandscape ? 'aspectFit' : 'widthFix'
            return { width, height, top, left, scaleMode }
        }).catch((reason) => {
            console.log(reason)
            return { width: '0px', height: '0px', top: '0px', left: '0px', scaleMode: 'widthFix' }
        })
        return layout
    }

    setDataChangedListener = (dataChangedListener: (images: PageImages) => void) => {
        this.dataChangedListener = dataChangedListener
    }

    setAnimationChangedListener = (animationChangedListener: (animations: PageAnimations) => void) => {
        this.animationChangedListener = animationChangedListener
    }

    setAnimationListener = (animationListener: AnimationProcessListener) => {
        this.animationProcessListener = animationListener
    }

    setCacheManager = (cacheManager: CacheManager) => {
        this.cacheManager = cacheManager
    }

    goActionAnimation = (action: PageAction) => {
        this.animationQueue.push({
            type: EventType.Click,
            action: action,
            deg: 0,
            start: { x: 0, y: 0 },
            last: { x: 0, y: 0 },
            moves: new Queue<EventPoint>(),
            isMove: false,
            isEnd: true,
            startTime: +new Date(),
            lastTime: +new Date()
        })
        this.executeAnimation()
    }

    isTouchEvent = () => {
        return this.touchEvent
    }

    onTouchStartEvent = (point: EventPoint) => {
        this.animationQueue.push({
            type: EventType.Touch,
            action: PageAction.None,
            deg: 0,
            start: point,
            last: point,
            moves: new Queue<EventPoint>(),
            isMove: false,
            isEnd: false,
            startTime: +new Date(),
            lastTime: +new Date()
        })
    }

    onTouchMoveEvent = (movePoint: EventPoint) => {
        this.touchEvent = true
        if (this.animationQueue.isNotEmpty()) {
            const animation = this.animationQueue.last()
            if (animation) {
                animation.action = this.getTouchPageAction(animation.start, movePoint)
                animation.moves.push(movePoint)
                animation.last = movePoint
                animation.lastTime = +new Date()
            }
            this.executeAnimation()
        }
    }

    onTouchEndEvent = (): boolean => {
        const animation = this.animationQueue.last()
        if (animation) {
            animation.isEnd = true
            animation.lastTime = +new Date()
            if (this.touchEvent) {
                this.touchEvent = false
            } else {
                const action = this.getPageAction(animation.start)
                if (action === PageAction.None) {
                    this.animationQueue.remove()
                    return false
                } else {
                    animation.action = action
                    animation.type = EventType.Click
                    this.executeAnimation()
                }
            }
        }
        return true
    }

    executeAnimation = () => {
        let delay = Math.round(1e3 / 20)
        if (!this.animationQueueRuning) {
            const running = () => {
                clearTimeout(this.animationQueueTimer)
                this.animationQueueTimer = setTimeout(() => {
                    if (this.animationQueue.isNotEmpty()) {
                        // console.log('Queue running!')
                        if (!this.animationRunning) {
                            this.animationQueueProcess()
                        }
                        running()
                    } else {
                        this.animationQueueRuning = false
                    }
                }, delay)
            }
            running()
            this.animationQueueRuning = true
        }
    }

    animationQueueProcess = () => {
        const animation = this.animationQueue.first()
        if (animation) {
            switch (animation.type) {
                case EventType.Click:
                    const action = animation.action
                    if (this.canTurn(action)) {
                        action === PageAction.Next ? this.next() : this.prev()
                        this.animationStart(action, () => {
                            this.animationProcess(action, 180, 0, () => {
                                this.animationEnded(true, () => {
                                    this.animationQueue.peek()
                                    return this.animationQueue.isEmpty()
                                })
                            })
                        })
                    } else {
                        this.animationQueue.clear()
                        this.animationStart(PageAction.None, () => {
                            this.animationProcess(action, 45, 0, () => {
                                this.animationReset(action, 0, () => {
                                    this.animationProcessListener.end(false, true)
                                })
                            })
                        })
                    }
                    break
                case EventType.Touch:
                    this.touchAnimationFragment(animation)
                    break
                default:
                    this.animationRunning = false
                    break
            }
        }
    }

    touchAnimationFragment = (animation: AnimationData) => {
        if (this.canTurn(animation.action)) {
            const running = () => {
                this.fragmentRunning = true
                let delay = Math.round(1e3 / 60)
                if (!animation.isMove) {
                    animation.isMove = true
                    this.animationStart(animation.action)
                    delay = this.startDuration
                } else if (animation.moves.isNotEmpty()) {
                    const point = animation.moves.peek()
                    animation.deg = this.getRotateAngle(animation.action, point!)
                    this.animationProcess(animation.action, animation.deg, 0)
                } else if (animation.isEnd) {
                    this.fragmentRunning = false
                    this.touchAnimationEnded(animation)
                    delay = this.procDuration + this.dataDuration + this.endedDuration
                }
                if (this.fragmentRunning) {
                    // console.log('Fragment running!')
                    clearTimeout(this.fragmentTimer)
                    this.fragmentTimer = setTimeout(running, delay)
                }
            }
            running()
        } else {
            this.animationQueue.clear()
            this.animationStart(PageAction.None, () => {
                this.animationProcess(animation.action, 45, 0, () => {
                    this.animationReset(animation.action, 0, () => {
                        this.animationProcessListener.end(false, true)
                    })
                })
            })
        }

    }

    touchAnimationEnded = (animation: AnimationData) => {
        const diffTime = animation.lastTime - animation.startTime
        if (diffTime < 300) {
            animation.action === PageAction.Next ? this.next() : this.prev()
            this.animationProcess(animation.action, 180, 0, () => {
                this.animationEnded(true, () => {
                    this.animationQueue.peek()
                    return this.animationQueue.isEmpty()
                })
            })
        } else {
            if (animation.deg > 60) {
                animation.action === PageAction.Next ? this.next() : this.prev()
                this.animationProcess(animation.action, 180, 0, () => {
                    this.animationEnded(true, () => {
                        this.animationQueue.peek()
                        return this.animationQueue.isEmpty()
                    })
                })
            }
            if (animation.deg < 60) {
                this.animationReset(animation.action, 0, () => {
                    this.animationQueue.clear()
                    this.animationProcessListener.end(true, true)
                })
            }
        }
    }

    animationStart = (action: PageAction, executed?: () => void) => {
        this.animationRunning = true
        this.animationChangedListener(this.createOpacityAnimation(0))
        this.animationProcessListener.start(action)
        clearTimeout(this.startTimer)
        this.startTimer = setTimeout(() => {
            if (executed) {
                executed()
            }
        }, this.startDuration)
    }

    animationProcess = (action: PageAction, deg: number, opacity: number, executed?: () => void) => {
        const animations = this.createRotateAnimation(action, deg, opacity)
        this.animationChangedListener(animations)
        clearTimeout(this.processTimer)
        this.processTimer = setTimeout(() => {
            if (executed) {
                executed()
            }
        }, this.procDuration)
    }

    animationEnded = (isPlay: boolean, executed?: () => boolean) => {
        const animations: PageAnimations = this.createOpacityAnimation(1)
        this.animationChangedListener(animations)
        clearTimeout(this.processTimer)
        this.processTimer = setTimeout(() => {
            this.dataChangedListener(this.getFlipPages())
            clearTimeout(this.endedTimer)
            this.endedTimer = setTimeout(() => {
                this.animationRunning = false
                if (executed) {
                    this.animationProcessListener.end(isPlay, executed())
                }
            }, this.dataDuration)
        }, this.endedDuration)
    }

    animationReset = (action: PageAction, opacity: number, executed?: () => void) => {
        const animations = this.createRotateAnimation(action, 0, opacity)
        this.animationChangedListener(animations)
        clearTimeout(this.endedTimer)
        this.endedTimer = setTimeout(() => {
            this.animationRunning = false
            if (executed) {
                executed()
            }
        }, this.procDuration)
    }

    createRotateAnimation = (action: PageAction, deg: number, opacity: number): PageAnimations => {
        let backDeg = 180
        let frontDeg = 0
        switch (action) {
            case PageAction.Prev:
                backDeg += deg
                frontDeg += deg
                break
            case PageAction.Next:
                backDeg -= deg
                frontDeg -= deg
                break
        }
        return {
            back: {
                transition: 'transform ' + (this.procDuration) + 'ms ease-out',
                transform: 'rotateY(' + backDeg + 'deg)'
            },
            front: {
                transition: 'transform ' + (this.procDuration) + 'ms ease-out',
                transform: 'rotateY(' + frontDeg + 'deg) translateZ(0)'
            },
            buffer: {
                transition: 'transform ' + (this.startDuration) + 'ms linear',
                opacity
            }
        }
    }

    createOpacityAnimation = (opacity: number): PageAnimations => {
        const animations: PageAnimations = {
            back: {},
            front: {},
            buffer: {
                transition: 'transform ' + (this.startDuration) + 'ms linear',
                opacity
            }
        }
        return animations
    }

    getRotateAngle = (action: PageAction, point: EventPoint): number => {
        let diff: number
        if (this.isLandscape) {
            diff = action === PageAction.Next ? Math.abs(point.y - this.scene.width) : point.y
        } else {
            diff = action === PageAction.Next ? Math.abs(point.x - this.scene.width) : point.x
        }
        const angle = Math.floor(diff / this.scene.width * 180)
        return angle
    }

    getTouchPageAction = (startPoint: EventPoint, lastPoint: EventPoint) => {
        if (this.isLandscape) {
            const yDiff = startPoint.y - lastPoint.y
            return yDiff > 0 ? PageAction.Next : PageAction.Prev
        } else {
            const xDiff = startPoint.x - lastPoint.x
            return xDiff > 0 ? PageAction.Next : PageAction.Prev
        }
    }

    canTurn = (action: PageAction): boolean => {
        const exec = action === PageAction.Next ? this.hasNext() : this.hasPrev()
        return exec
    }

    getPageAction = (point: { x: number, y: number }): PageAction => {
        const space: number = this.scene.width / 3
        let action: PageAction
        let value: number = 0
        if (this.isLandscape) {
            value = point.y
        } else {
            value = point.x
        }
        if (value < space) {
            action = PageAction.Prev
        } else if (value > this.scene.width - space) {
            action = PageAction.Next
        } else {
            action = PageAction.None
        }
        return action
    }

    getFlipPages = (): PageImages => {
        return {
            before: this.getPrev(),
            current: this.getCurr(),
            after: this.getNext()
        }
    }

    setPage = (page: number): void => {
        this.page = page
        this.init()
    }

    getPage = (): number => {
        return this.page
    }

    getTotal = (): number => {
        return this.total
    }

    getPrev = (): string => {
        return this.hasPrev() ? this.cacheManager.getCacheUrl(this.data[this.page - 1]) : emptyImg
    }

    getNext = (): string => {
        return this.hasNext() ? this.cacheManager.getCacheUrl(this.data[this.page + 1]) : emptyImg
    }

    getCurr = (): string => {
        return this.cacheManager.getCacheUrl(this.data[this.page])
    }

    isFirst = (): boolean => {
        return this.page === 0
    }

    isLast = (): boolean => {
        return this.page === (this.total - 1)
    }

    hasPrev = (): boolean => {
        return this.isFirst() ? false : true
    }

    hasNext = (): boolean => {
        return this.isLast() ? false : true
    }

    prev = () => {
        if (this.hasPrev()) {
            --this.page
        }
    }

    next = () => {
        if (this.hasNext()) {
            ++this.page
        }
    }
}
interface AnimationAttributes {
    transition?: string,
    transform?: string,
    opacity?: any
}
enum EventType {
    Click = 0, Touch = 1
}
interface EventPoint {
    x: number,
    y: number
}
interface AnimationData {
    type: EventType
    action: PageAction,
    deg: number,
    start: EventPoint,
    last: EventPoint,
    moves: Queue<EventPoint>,
    isMove: boolean,
    isEnd: boolean,
    startTime: number,
    lastTime: number
}
export interface AnimationProcessListener {
    start: (action: PageAction) => void
    end: (isPlay: boolean, allEnd: boolean) => void
}
export type ScaleMode = 'aspectFit' | 'widthFix'
export enum PageAction {
    Prev = -1, None = 0, Next = 1
}
export interface PageImages {
    before: string,
    current: string,
    after: string
}
export interface PageAnimations {
    back: AnimationAttributes,
    front: AnimationAttributes,
    buffer: AnimationAttributes
}
